#include <limits>
#include <cassert>
#include <cerrno>
#include "xstdlib.h"
#include "xbdb.h"

using namespace std;
#define CASE_ERROR(a) case a: THROW(#a); break

/* used to track the state of a database . Once it is close, you cannot reopen it */
enum {
    NEVER_OPEN,
    OPENED,
    CLOSED
    };


std::ostream& operator<<(std::ostream& out,const bdb::Entry& o)
	{
	out << "entry{"<< o.size() << "}[";
	for(size_t i=0;i< o.size();++i)
		{
		if(isprint(o[i]))
			{
			out << o[i];
			}
		else
			{
			out << "\\0"<<(int)o[i];
			}
		}
	out <<"]";
	return out;
	}

bdb::Entry::Entry():ptr(NULL),len(0UL)
    {

    }


bdb::Entry::Entry(size_t len,int c):ptr(NULL),len(len)
	{
	ptr=(char*)safeMalloc(sizeof(char)*len);
	std::memset((void*)ptr,c,len);
	}

bdb::Entry::Entry(const bdb::Entry& cp):ptr(NULL),len(cp.size())
	{
	ptr=(char*)safeMalloc(sizeof(char)*(len));
	memcpy((void*)ptr,cp.data(),len);
	}
bdb::Entry::Entry(const DBT* dbt):ptr(NULL),len(0UL)
	{
	if(dbt->flags!=0) THROW("not handled");
	len=dbt->size;
	ptr=(char*)safeMalloc(sizeof(char)*(len));
	memcpy((void*)ptr,dbt->data,len);
	}


bdb::Entry::Entry(const void* s,std::size_t len):ptr(NULL),len(len)
	{
	ptr=(char*)safeMalloc(sizeof(char)*(len));
	memcpy((void*)ptr,s,len);
	}
bdb::Entry::Entry(const char* s):ptr(NULL),len(strlen(s))
	{
	ptr=(char*)safeMalloc(sizeof(char)*(len));
	memcpy((void*)ptr,s,len);
	}
bdb::Entry::Entry(const std::string& s):ptr(NULL),len(s.size())
	{
	ptr=(char*)safeMalloc(sizeof(char)*(len));
	memcpy((void*)ptr,s.data(),len);
	}


bdb::Entry::~Entry()
	{
	free(ptr);
	}

bool bdb::Entry::empty() const
	{
	return len==0UL;
	}

const void* bdb::Entry::data() const
	{
	return ptr;
	}
size_t bdb::Entry::size() const
	{
	return len;
	}

std::string bdb::Entry::toString() const
	{
	ostringstream os;
	os.write(ptr,len);
	return os.str();
	}

bdb::Entry& bdb::Entry::append(const void* p,size_t n)
	{
	ptr=(char*)safeRealloc(ptr,sizeof(char)*(n+len));
	memcpy((void*)&ptr[len],p,n);
	len+=n;
	return *this;
	}
void bdb::Entry::fill(DBT* dbt) const
	{
	memset((void*)dbt,0,sizeof(DBT));
	dbt->data=(void*)this->data();
	dbt->size=this->size();
	}

bool bdb::Entry::operator==(const Entry& cp) const
	{
	if(this==&cp) return true;
	if(size()!=cp.size()) return false;
	return memcmp((const void*)data(), (const void*)cp.data(),cp.size())==0;
	}

bool bdb::Entry::operator<(const bdb::Entry& cp) const
	{
	size_t i=0;
	for(;;)
		{
		if(i==size() && i==cp.size()) break;
		if(i==size()) return -1;
		if(i==cp.size()) return 1;
		int c1=   ptr[i];
		int c2=cp.ptr[i];
		if(c1!=c2) return c1<c2;
		++i;
		}
	return 0;
	}

bdb::Entry& bdb::Entry::operator=(const bdb::Entry& cp)
	{
	if(this!=&cp)
		{
		len=cp.len;
		ptr=(char*)safeRealloc(ptr,sizeof(char)*(len));
		memcpy((void*)ptr,(const void*)cp.ptr,len);
		}
	return *this;
	}

char bdb::Entry::operator[](std::size_t index) const
	{
	if(index>=size()) THROW("Out of bound");
	return ptr[index];
	}

/*******************************************************************************/

bdb::EntryReader::EntryReader(const Entry* entry):
		entry_data(entry->data()),
		entry_size(entry->size()),
		pos(0UL)
	{

	}
bdb::EntryReader::EntryReader(const DBT* dbt):
		entry_data(dbt->data),
		entry_size(dbt->size),
		pos(0UL)
	{
	if(dbt->flags!=0) THROW("Not handled");
	}
bdb::EntryReader::~EntryReader()
	{

	}
std::size_t bdb::EntryReader::readbytes(void *ptr, size_t size)
	{
	size_t toread=std::min(size,entry_size-pos);
	memcpy(ptr,(void*)(&((const char*)entry_data)[pos]),toread);
	pos+=toread;
	return toread;
	}

bdb::EntryReader& bdb::EntryReader::read(void *ptr, size_t size)
	{
	if(readbytes(ptr,size)!=size) THROW("Cannot read "<< size << " bytes");
	return *this;
	}

std::string bdb::EntryReader::readCString()
	{
	std::string s;
	char c;
	while((c=readChar())!='\0')
		{
		s+=c;
		}
	return s;
	}
std::string bdb::EntryReader::readString8()
	{
	size_t L= readUInt8();
	std::string s(L,'\0');
	read((void*)s.data(),L);
	return s;
	}

std::string bdb::EntryReader::readString16()
	{
	size_t L= readUInt16();
	std::string s(L,'\0');
	read((void*)s.data(),L);
	return s;
	}
std::string bdb::EntryReader::readString32()
	{
	size_t L= readUInt32();
	std::string s(L,'\0');
	read((void*)s.data(),L);
	return s;
	}


/*******************************************************************************/
bdb::EntryWriter::EntryWriter(bdb::Entry* entry):entry(entry)
	{

	}
bdb::EntryWriter::~EntryWriter()
	{

	}

std::size_t bdb::EntryWriter::writebytes(const void *ptr, size_t size)
	{
	entry->append(ptr,size);
	return size;
	}
bdb::EntryWriter& bdb::EntryWriter::write(const void *ptr, size_t size)
	{
	this->writebytes( ptr,size);
	return *this;
	}


bdb::EntryWriter& bdb::EntryWriter::writeCString(const char *ptr)
	{
	char* p=(char*)ptr;
	for(;;)
		{
		writebytes(p,sizeof(char));
		if(*p==0) break;
		++p;
		}
	return *this;
	}

bdb::EntryWriter& bdb::EntryWriter::writeString8(const std::string s)
	{
	if(s.size()> numeric_limits<uint8_t>::max()) THROW("String too large:"<< s.size());
	writeUInt8((uint8_t)s.size());
	return write(s.data(),s.size());
	}
bdb::EntryWriter& bdb::EntryWriter::writeString16(const std::string s)
	{
	if(s.size()> numeric_limits<uint16_t>::max() ) THROW("String too large:"<< s.size());
	writeUInt16((uint16_t)s.size());
	return write(s.data(),s.size());
	}
bdb::EntryWriter& bdb::EntryWriter::writeString32(const std::string s)
	{
	if(s.size()> numeric_limits<uint32_t>::max()) THROW("String too large:"<< s.size());
	writeUInt32((uint32_t)s.size());
	return write(s.data(),s.size());
	}
/*******************************************************************************/
bdb::Database::Database(DB_ENV *dbenv,u_int32_t flag):dbp(NULL),flag_status((int)NEVER_OPEN)
	{
	int ret;
	if((ret=db_create(&dbp, dbenv, flag)) != 0)
		{
		THROW("Cannot create db "<< db_strerror(ret));
		}
	}

DB* bdb::Database::db()
	{
	return dbp;
	}

uint64_t bdb::Database::count(DB_TXN *txn,u_int32_t flag)
    {
    uint64_t n=0UL;
    auto_ptr<Cursor> c= cursor(txn,flag);
    while(c->next())
	    {
	    ++n;
	    }
    c.reset();
    return n;
    }

bool bdb::Database::is_open() const
	{
	return this->flag_status==(int)OPENED;
	}

bdb::Database::~Database()
	{
	if(dbp!=NULL  && flag_status==OPENED) dbp->close(dbp,0);
	}


void bdb::Database::close()
	{
	if(dbp!=NULL && flag_status==OPENED)
		{
		int ret;
		if((ret=dbp->close(dbp,0))!=0 && this->flag_status==(int)OPENED)
			{
			cerr << "Warning: cannot close database "<< db_strerror(ret) << endl;
			}
		}
	this->flag_status=(int)CLOSED;
	}

void bdb::Database::open(DB_TXN *txn,char *file, const char *database, DBTYPE type, u_int32_t flags, int mode)
	{
	switch(this->flag_status)
		{
		case OPENED:
			{
			THROW("database "<<
					" file:"<< (file==NULL?"<nil>":file)<<
					" db:"<< (database==NULL?"<nil>":database)<< ""
					" already opened");
			break;
			}
		case CLOSED:
			{
			THROW("database " <<
					" file:"<< (file==NULL?"<nil>":file)<<
					" db:"<< (database==NULL?"<nil>":database)<<
					" was closed. You cannot re-open it");
			break;
			}
		case NEVER_OPEN:break;
		default:THROW("Illegal state");
		}
	if(dbp==NULL) THROW("Illegal State dbp=NULL");
	int ret=0;
	if((ret=dbp->open(dbp,txn,file,database,type,flags,mode))!=0)
		{
		THROW("Cannot open database "<< (file==NULL?"nofile":file) << " "<< (database==NULL?"noname":database)<<" "<<
				db_strerror(ret));
		}
	this->flag_status=OPENED;
	}

int bdb::Database::put(DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags)
	{
	return dbp->put(dbp,txnid,key,data,flags);
	}

int bdb::Database::put(DB_TXN *txnid,const  Entry *key,const  Entry *data, u_int32_t flags)
	{
	DBT k,v;
	key->fill(&k);
	data->fill(&v);
	int ret= put(txnid,&k,&v,flags);
	switch(ret)
		{
		case 0: break;
		THROW("Put failed: ("<< ret<< ") "<< db_strerror(ret)); break;
		}
	return ret;
	}

int bdb::Database::get(DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags)
	{
	return dbp->get(dbp,txnid,key,data,flags);
	}


std::auto_ptr<bdb::Entry> bdb::Database::get(DB_TXN *txnid, const bdb::Entry *key, u_int32_t flags)
	{
	DBT k,v;
	key->fill(&k);
	std::memset((void*)&v,0,sizeof(DBT));
	if( get(txnid,&k,&v,flags)!=0)
		{
		return std::auto_ptr<bdb::Entry>(NULL);
		}
	return std::auto_ptr<bdb::Entry>(new Entry(&v));
	}

int bdb::Database::pget(DB_TXN *txnid, DBT *key, DBT *pkey, DBT *data, u_int32_t flags)
	{
	return dbp->pget(dbp,txnid,key,pkey,data,flags);
	}
int bdb::Database::remove(DB_TXN *txnid, DBT *key, u_int32_t flags)
	{
	return dbp->del(dbp,txnid,key,flags);
	}

bool bdb::Database::contains(DB_TXN *txnid, const bdb::Entry *key, u_int32_t flags)
	{
	DBT k,v;
	key->fill(&k);
	std::memset((void*)&v,0,sizeof(DBT));
	return get(txnid,&k,&v,flags)==0;
	}

int bdb::Database::cursor( DB_TXN *txnid, DBC **cursorp, u_int32_t flags)
	{
	return dbp->cursor(dbp,txnid,cursorp,flags);
	}
std::auto_ptr<bdb::Cursor> bdb::Database::cursor(DB_TXN *txnid, u_int32_t flags)
	{
	DBC* c=NULL;
	int ret=dbp->cursor(dbp,txnid,&c,flags);
	if(ret!=0) THROW("Cannot create cursor . Error ("<< ret<<")"<< db_strerror(ret) << " flags:"<< flags);
	return std::auto_ptr<bdb::Cursor>(new bdb::Cursor(c));
	}
int bdb::Database::setFlags(u_int32_t flags)
	{
	int ret=dbp->set_flags(dbp, flags);
	if(ret!=0) THROW("Cannot set_flags "<< db_strerror(ret));
	return ret;
	}
bdb::Database& bdb::Database::setBTreeCompare(Database::bt_comparator fun)
	{
	int ret=dbp->set_bt_compare(dbp,fun);
	if(ret!=0) THROW("Cannot set BtCompare");
	return *this;
	}

bdb::Database& bdb::Database::setDupCompare(Database::bt_comparator fun)
	{
	int ret=dbp->set_dup_compare(dbp,fun);
	if(ret!=0) THROW("Cannot set DupCompare");
	return *this;
	}
bdb::Database& bdb::Database::setHashCompare(Database::bt_comparator fun)
	{
	THROW("Not implemented");
	return *this;
	}

int  bdb::Database::sync(u_int32_t flags)
	{
	int ret=dbp->sync(dbp,flags);
	if(ret!=0)
		{
		THROW("Cannot sync database "<< db_strerror(ret));
		}
	return ret;
	}

#define DB_CMP(fun,type) \
int bdb::Database::compare##fun(DB *db, const DBT *dbt1, const DBT *dbt2)\
			{\
			type v1;\
			type v2;\
			assert(dbt1->size>=sizeof(type));\
			assert(dbt2->size>=sizeof(type));\
			std::memcpy((void*)&v1,dbt1->data,sizeof(type));\
			std::memcpy((void*)&v2,dbt2->data,sizeof(type));\
			return v1==v2?0:(v1<v2?-1:1);\
			}
DB_CMP(Int8,int8_t)
DB_CMP(Int16,int16_t)
DB_CMP(Int32,int32_t)
DB_CMP(Int64,int64_t)
DB_CMP(UInt8,uint8_t)
DB_CMP(UInt16,uint16_t)
DB_CMP(UInt32,uint32_t)
DB_CMP(UInt64,uint64_t)
DB_CMP(Float,float)
DB_CMP(Double,double)

int bdb::Database::setCacheSize( u_int32_t gbytes, u_int32_t bytes, int ncache)
	{
	int ret=db()->set_cachesize(db(),gbytes,bytes,ncache);
	if(ret!=0) THROW("Cannot set cache size "<< gbytes << " " << bytes << " "<< db_strerror(ret));
	return ret;
	}

int bdb::Database::compareIString(DB *db, const DBT *dbt1, const DBT *dbt2)
	{
	u_int32_t i=0;
	for(;;)
		{
		if(i==dbt1->size && i==dbt2->size) break;
		if(i==dbt1->size) return -1;
		if(i==dbt2->size) return 1;
		int c1=toupper(((char*)dbt1->data)[i]);
		int c2=toupper(((char*)dbt2->data)[i]);
		if(c1!=c2) return c1-c2;
		++i;
		}
	return 0;
	}

#undef DB_CMP

/****************************************************************/

bdb::Cursor::Cursor(DBC *cursor):cursorp(cursor)
	{
	std::memset(&keyE,0,sizeof(DBT));
	std::memset(&dataE,0,sizeof(DBT));
	}


bdb::Cursor::~Cursor()
	{
	if (cursorp != NULL)  cursorp->c_close(cursorp);
	}

void bdb::Cursor::close()
	{
	if (cursorp != NULL)  cursorp->c_close(cursorp);
	cursorp=NULL;
	}

DBT* bdb::Cursor::key()
	{
	return &keyE;
	}
DBT* bdb::Cursor::data()
	{
	return &dataE;
	}


int bdb::Cursor::get(DBT *k, DBT *d, u_int32_t flags)
    {
    memcpy((void*)&keyE,(void*)k,sizeof(DBT));
    memcpy((void*)&dataE,(void*)d,sizeof(DBT));
    return get(flags);
    }



/* http://download.oracle.com/docs/cd/E17076_02/html/api_reference/C/dbcget.html */
int bdb::Cursor::get(u_int32_t flags)
	{
	int ret= cursorp->c_get(cursorp,&keyE,&dataE,flags);
	switch(ret)
		{
		case 0://through
		case DB_NOTFOUND://through
		case DB_KEYEXIST://through
			break;
		default: THROW("Cursor failed "<< db_strerror(ret));break;
		}
	return ret;
	}

std::auto_ptr<bdb::Entry> bdb::Cursor::keyEntry()
	{
	return std::auto_ptr<Entry>(new Entry(key()));
	}
std::auto_ptr<bdb::Entry> bdb::Cursor::dataEntry()
	{
	return std::auto_ptr<Entry>(new Entry(data()));
	}
int bdb::Cursor::put(DBT *key, DBT *data, u_int32_t flags)
	{
	return cursorp->c_put(cursorp,key,data,flags);
	}

#define CTOSTR(fun,key) std::string bdb::Cursor::fun##ToString() \
	{\
	return string((const char*)key.data,key.size);\
	}
CTOSTR(key,keyE)
CTOSTR(data,dataE)
#undef CTOSTR

#define CURSOR_CPY(dbt) \
	if(dbt.size<(n+index)) THROW("Cannot copy("<< index<<"+"<<n<<") because DBT.size="<< dbt.size);\
	std::memcpy(ptr,(void*)(&((char*)dbt.data)[index]),n);
void  bdb::Cursor::keyCopyTo(void* ptr,std::size_t index,std::size_t n) const
	{
	CURSOR_CPY(keyE);
	}
void  bdb::Cursor::dataCopyTo(void* ptr,std::size_t index,std::size_t n) const
	{
	CURSOR_CPY(dataE);
	}
#undef CURSOR_CPY


/****************************************************************/
bdb::Environment::Environment():envptr(NULL),is_open(false)
	{
	int ret = db_env_create(&envptr, 0);
	if(ret!=0) THROW("Cannot db_env_create ("<< ret << ") " << db_strerror(ret));
	envptr->set_errfile(envptr, stderr);
	}
	
bdb::Environment::~Environment()
	{
	if(envptr!=NULL && is_open)  envptr->close(envptr, 0);
	}

DB_ENV* bdb::Environment::env()
	{
	return envptr;
	}
void bdb::Environment::close()
	{
	if(envptr!=NULL && is_open)  envptr->close(envptr, 0);
	is_open=false;
	}

void bdb::Environment::open( char *db_home, u_int32_t flags, int mode)
	{
	if(is_open) THROW("Already opened");
	int ret= envptr->open(envptr,db_home,flags,mode);
	if(ret!=0) THROW("Cannot open env (err="<< ret << ")  db_home=\""
			<< (db_home==NULL?"null":db_home)
			<< "\" flags:"<< flags << " mode:"<< mode << " "
			<< db_strerror(ret)<<" "
			<< strerror(ret)<< " "
			"\nBDBD.version:"
			<< DB_VERSION_STRING);
	is_open=true;
	}

int bdb::Environment::setCacheSize( u_int32_t gbytes, u_int32_t bytes, int ncache)
	{
	int ret=env()->set_cachesize(env(),gbytes,bytes,ncache);
	if(ret!=0) THROW("Cannot set cache size "<< gbytes << " " << bytes << " "<< db_strerror(ret));
	return ret;
	}
